package cfca.seal.front.service.servlet;

import cfca.com.itextpdf.text.pdf.PdfReader;
import cfca.com.itextpdf.text.pdf.security.BouncyCastleDigest;
import cfca.com.itextpdf.text.pdf.security.ExternalDigest;
import cfca.sadk.algorithm.common.Mechanism;
import cfca.sadk.algorithm.common.PKCS7SignedData;
import cfca.sadk.algorithm.common.PKIException;
import cfca.sadk.lib.crypto.JCrypto;
import cfca.sadk.lib.crypto.Session;
import cfca.sadk.util.Base64;
import cfca.sadk.util.HashUtil;
import cfca.sadk.util.Signature;
import cfca.sadk.x509.certificate.X509CRL;
import cfca.sadk.x509.certificate.X509Cert;
import cfca.seal.bean.OperationLog;
import cfca.seal.bean.Seal;
import cfca.seal.dao.main.IOperationLogDao;
import cfca.seal.dao.main.ISealDao;
import cfca.seal.exception.CodeException;
import cfca.seal.front.service.FunctionType;
import cfca.seal.front.service.ws.IAssistSealService;
import cfca.seal.front.service.ws.bean.ClientSealBean;
import cfca.seal.front.service.ws.util.ValidityUtil;
import cfca.seal.mananger.CrlManager;
import cfca.seal.mananger.OperationLogManager;
import cfca.seal.sadk.DonePdfSeal;
import cfca.seal.sadk.PrePdfReader;
import cfca.seal.sadk.PrePdfSeal;
import cfca.seal.sadk.PrePdfSealExtra;
import cfca.seal.sadk.security.external.DoneExternalSessionSignature;
import cfca.seal.sadk.security.external.DoneExternalSessionSignatureExtra;
import cfca.seal.sadk.security.external.ExternalSessionSignatureExtra;
import cfca.seal.sadk.security.external.PreExternalSessionSignatureParameter;
import cfca.seal.system.Env;
import cfca.seal.util.ImageUtil;
import cfca.seal.util.StringUtil;
import cfca.seal.util.TimeUtil;
import cfca.seal.util.VerifyUtil;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

public class ClientSealServlet extends HttpServlet
{
  private static final Logger logger = LoggerFactory.getLogger("service_sys");
  private static final long serialVersionUID = -2802334706155081655L;
  private Map<String, String> hashMap = new HashMap();
  private Map<String, String> certMap = new HashMap();
  private Map<String, ClientSealBean> strategyMap = new HashMap();
  private Map<String, String> signedMap = new HashMap();
  private Map<String, byte[]> doneMap = new HashMap();
  private Map<String, byte[]> imageMap = new HashMap();

  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
  {
    doPost(request, response);
  }

  public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    OperationLog operationLog = null;
    OutputStream os = response.getOutputStream();

    ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getSession().getServletContext());
    IOperationLogDao operationDao = (IOperationLogDao)applicationContext.getBean("operationLogDao");

    String type = request.getParameter("type");
    String message = "";
    if ("autoCalculatePdfHash".equals(type))
    {
      try {
        logger.info("自动化计算Pdf签章Hash值开始");
        operationLog = OperationLogManager.getInstance(FunctionType.sealAutoPdf.toString(), "自动化计算Pdf签章Hash值");

        String sessionId = request.getSession().getId();
        operationLog.setObjectId(sessionId);
        String pdfString = formatMessage(request.getParameter("pdf"));
        int pdfMaxPage = 0;
        byte[] pdfData = "".getBytes("UTF-8");
        if (StringUtil.isEmpty(pdfString)) {
          throw new CodeException("C3007", "PDF文档不能为空");
        }
        pdfData = Base64.decode(pdfString.getBytes("UTF-8"));
        PrePdfReader prePdfReader = new PrePdfReader(pdfData);
        pdfMaxPage = prePdfReader.getPdfReader().getNumberOfPages();

        String certBase64 = new String(Base64.decode(formatMessage(request.getParameter("x509Cert")).getBytes("UTF-8")));
        checkCertInfo(certBase64);
        this.certMap.put(sessionId, certBase64);

        String strategyXML = new String(Base64.decode(formatMessage(request.getParameter("strategyXML")).getBytes("UTF-8")), "UTF-8");

        ClientSealBean clientSealBean = new ClientSealBean(strategyXML);
        ValidityUtil.checkParamForClientSeal(clientSealBean, pdfMaxPage);
        this.strategyMap.put(sessionId, clientSealBean);

        if ("3".equals(clientSealBean.getType())) {
          logger.info("签章类型：关键字签章，判断PDF中关键字是否唯一");
          IAssistSealService assistSealService = (IAssistSealService)applicationContext.getBean("assistSealServiceImpl");
          String result = assistSealService.getKeywordLocationsInPdf(pdfData, clientSealBean.getKeyword());
          logger.info("获取关键字位置结果：" + result);
          if ("200".equals(StringUtil.getNodeText(result, "Code"))) {
            String locations = StringUtil.getNodeText(result, "List");
            if ((StringUtil.isEmpty(locations)) || (locations.indexOf("</Location><Location>") > 0))
              throw new CodeException("C3065", "存在多个关键字，无法进行Pdf合成外部签名");
          } else {
            throw new CodeException("C1010", "没有找到相应的关键字");
          }
        }

        String clientSealCode = StringUtil.getNodeText(strategyXML, "ClientSealCode");
        String iamgeBase64 = StringUtil.getNodeText(strategyXML, "ImageBase64");
        byte[] iamgeData = "".getBytes();
        Seal seal = null;
        if (StringUtil.isNotEmpty(clientSealCode)) {
          ISealDao sealDao = (ISealDao)applicationContext.getBean("sealDao");
          seal = sealDao.getSealByCode(clientSealCode);
          if (null != seal) {
            iamgeData = seal.getSealImageData();
            logger.info("使用客户端印章图片盖章");
          } else {
            logger.info("客户端印章不存在，使用传入的图片盖章");
          }
        }
        if (((StringUtil.isEmpty(clientSealCode)) || (null == seal)) && (StringUtil.isNotEmpty(iamgeBase64))) {
          iamgeData = ImageUtil.transferAlpha(Base64.decode(iamgeBase64));
          logger.info("客户端印章编码未输入或者输入的印章编码无效，使用传入的图片盖章");
        }
        if ((null == iamgeData) || (iamgeData.length == 0)) {
          logger.info("客户端印章编码和图片均未输入或者输入的印章编码无效，无图片盖章");
          throw new CodeException("C3067", "客户端印章编码和图片均未输入或者输入的印章编码无效，无图片盖章");
        }
        this.imageMap.put(sessionId, iamgeData);

        String base64HashValue = null;

        PdfThread sender = new PdfThread(sessionId, pdfData);
        sender.start();
        while (true)
        {
          synchronized (this.hashMap) {
            base64HashValue = (String)this.hashMap.get(sessionId);
            if (base64HashValue == null) {
              this.hashMap.wait();
            }
          }
          base64HashValue = (String)this.hashMap.get(sessionId);
          if (base64HashValue != null) {
            break;
          }
        }

        System.out.println("hashValue(Base64):" + base64HashValue);
        message = "<Result><Code>200</Code><Message>successfully!</Message><PdfSealHash>" + base64HashValue + "</PdfSealHash><Id>" + sessionId + "</Id></Result>";

        logger.info("自动化计算Pdf签章Hash值结束");
      } catch (CodeException ce) {
        operationLog.setResult(10);
        logger.error("接口在进行自动化计算Pdf签章Hash值发生失败! ", ce);
        message = "<Result><Code>" + ce.getCode() + "</Code><Message>" + ce.getMessage() + "</Message></Result>";
      } catch (Exception e) {
        operationLog.setResult(10);
        logger.error("接口在进行自动化计算Pdf签章Hash值发生失败! ", e);
        message = "<Result><Code>C0001</Code><Message>接口在进行自动化计算Pdf签章Hash值发生失败</Message></Result>";
      } finally {
        operationDao.insert(operationLog);
      }
      byte[] result = Base64.encode(message.getBytes("UTF-8"));
      os.write(result, 0, result.length);
    } else if ("synthesizeOuterSignature".equals(type)) {
      try {
        logger.info("Pdf合成外部签名开始");
        operationLog = OperationLogManager.getInstance(FunctionType.synthesizeOuterSignature.toString(), "Pdf合成外部签名");
        String sessionId = new String(Base64.decode(formatMessage(request.getParameter("id")).getBytes("UTF-8")));
        operationLog.setObjectId(sessionId);
        if (!this.hashMap.containsKey(sessionId)) {
          throw new CodeException("C3064", "随机数不一致");
        }
        String base64P7SignedData = formatMessage(request.getParameter("signature"));
        logger.info("webSign : " + base64P7SignedData);

        JCrypto.getInstance().initialize("JSOFT_LIB", null);
        Session session = JCrypto.getInstance().openSession("JSOFT_LIB");

        PKCS7SignedData pkcs7 = new PKCS7SignedData(session);
        pkcs7.loadBase64(base64P7SignedData.getBytes("UTF-8"));

        Signature sigUtil = new Signature();
        if (!sigUtil.p7VerifyByHash(Base64.decode((String)this.hashMap.get(sessionId)), base64P7SignedData.getBytes("UTF-8"), session)) {
          throw new CodeException("C9011", "签名验证失败");
        }
        X509Cert cert = new X509Cert(Base64.decode((String)this.certMap.get(sessionId)));
        if (!StringUtil.bytes2hex(cert.getEncoded()).equals(StringUtil.bytes2hex(pkcs7.getSignerX509Cert().getEncoded()))) {
          throw new CodeException("C9010", "证书不一致");
        }
        byte[] p1 = pkcs7.getSignature();
        this.signedMap.put(sessionId, new String(Base64.encode(p1), "UTF-8"));
        sign();

        byte[] doneValue = null;
        do {
          synchronized (this.doneMap) {
            doneValue = (byte[])this.doneMap.get(sessionId);
            if (doneValue == null) {
              this.doneMap.wait();
            }
          }
          doneValue = (byte[])this.doneMap.get(sessionId);
        }while (doneValue == null);
        this.doneMap.remove(sessionId);

        this.hashMap.remove(sessionId);
        this.certMap.remove(sessionId);
        this.imageMap.remove(sessionId);
        this.strategyMap.remove(sessionId);

        logger.info("Pdf合成外部签名结束");
        byte[] result = Base64.encode(doneValue);
        os.write(result, 0, result.length);
      } catch (CodeException ce) {
        operationLog.setResult(10);
        logger.error("接口在进行Pdf合成外部签名发生失败! ", ce);
        message = "<Error><ErrorCode>" + ce.getCode() + "</ErrorCode><ErrorMessage>" + ce.getMessage() + "</ErrorMessage></Error>";
        byte[] result = Base64.encode(message.getBytes("UTF-8"));
        os.write(result, 0, result.length);
      } catch (Exception e) {
        operationLog.setResult(10);
        logger.error("接口在进行Pdf合成外部签名发生失败! ", e);
        message = "<Error><ErrorCode>C0001</ErrorCode><ErrorMessage>接口在进行Pdf合成外部签名发生失败</ErrorMessage></Error>";
        byte[] result = Base64.encode(message.getBytes("UTF-8"));
        os.write(result, 0, result.length);
      } finally {
        if (null != os) {
          os.flush();
          os.close();
        }
        operationDao.insert(operationLog);
      }
    }
  }

  public void sign()
  {
    synchronized (this.signedMap) {
      this.signedMap.notifyAll();
    }
  }

  public void hashed() {
    synchronized (this.hashMap) {
      this.hashMap.notifyAll();
    }
  }

  public void done() {
    synchronized (this.doneMap) {
      this.doneMap.notifyAll();
    }
  }

  public String formatMessage(String msg) {
    return msg.replaceAll("%2B", "+").replaceAll("%2F", "/").replaceAll("%3D", "=").replaceAll("\n", "");
  }

  private void checkCertInfo(String certBase64) throws CodeException {
    X509Cert x509Cert = null;
    try {
      x509Cert = new X509Cert(Base64.decode(certBase64));
    } catch (PKIException pkiEx) {
      throw new CodeException("C6004", "X509Cert证书初始化数据失败", pkiEx);
    }

    if (!VerifyUtil.verifyCertDate(x509Cert, TimeUtil.getCurrentTime())) {
      throw new CodeException("C9001", "证书已过期或者还未生效");
    }

    X509CRL x509crl = CrlManager.getX509CRL(StringUtil.formatIssuerKey(x509Cert.getIssuer()));
    if ((x509crl != null) && (VerifyUtil.verifyByCRL(x509Cert, x509crl)))
      throw new CodeException("C9002", "证书已经吊销");
  }

  public class PdfThread extends Thread
  {
    String sessionId;
    String signValue;
    byte[] pdfData;

    public PdfThread(String sessionId, byte[] pdfData)
    {
      this.sessionId = sessionId;
      this.pdfData = pdfData;
    }

    public void run() {
      try {
        PrePdfReader prePdfReader = new PrePdfReader(this.pdfData);

        JCrypto.getInstance().initialize("JSOFT_LIB", null);
        Session mySession = JCrypto.getInstance().openSession("JSOFT_LIB");

        String certString = (String)ClientSealServlet.this.certMap.get(this.sessionId);
        X509Cert x509Cert = new X509Cert(Base64.decode(certString));
        DoneExternalSessionSignature externalSignature = new DoneExternalSessionSignature(null, "SHA-1", mySession);

        PreExternalSessionSignatureParameter externalSessionSignatureParameter = new PreExternalSessionSignatureParameter(x509Cert)
        {
          public byte[] getSignatureBytes(byte[] authenticateAttributeBytes) {
            try {
              String base64HashValue = Base64.toBase64String(HashUtil.RSAHashMessageByBC(authenticateAttributeBytes, new Mechanism("SHA-1"), false));

              ClientSealServlet.this.hashMap.put(PdfThread.this.sessionId, base64HashValue);
              ClientSealServlet.this.hashed();
              do
              {
                synchronized (ClientSealServlet.this.signedMap) {
                  PdfThread.this.signValue = ((String)ClientSealServlet.this.signedMap.get(PdfThread.this.sessionId));
                  if (PdfThread.this.signValue == null) {
                    ClientSealServlet.this.signedMap.wait();
                  }
                }

                PdfThread.this.signValue = ((String)ClientSealServlet.this.signedMap.get(PdfThread.this.sessionId));
              }while (PdfThread.this.signValue == null);
              ClientSealServlet.this.signedMap.remove(PdfThread.this.sessionId);

              return Base64.decode(PdfThread.this.signValue);
            } catch (Exception e) {
              e.printStackTrace();
            }
            return new byte[0];
          }
        };
        ExternalDigest externalDigest = new BouncyCastleDigest();
        ExternalSessionSignatureExtra externalSessionSignatureExtra = new DoneExternalSessionSignatureExtra(externalDigest, externalSignature, externalSessionSignatureParameter);

        DonePdfSeal donePdfSeal = new DonePdfSeal();
        donePdfSeal.initPdfReader(prePdfReader);

        byte[] pngFileData = (byte[])ClientSealServlet.this.imageMap.get(this.sessionId);
        PrePdfSeal prePdfSeal = new PrePdfSeal(null, null, externalSessionSignatureExtra, externalSessionSignatureParameter, pngFileData, 0.68F);
        donePdfSeal.initPdfSeal(prePdfSeal);

        PrePdfSealExtra prePdfSealExtra = new PrePdfSealExtra("Agreed", "CFCA");
        donePdfSeal.initPdfSealExtra(prePdfSealExtra);

        byte[] certPdfData = "".getBytes();
        ClientSealBean clientSealBean = (ClientSealBean)ClientSealServlet.this.strategyMap.get(this.sessionId);
        int certificationLevel = 0;
        if ("2".equals(clientSealBean.getType())) {
          String page = clientSealBean.getPage();
          int lx = Integer.parseInt(clientSealBean.getLx());
          int ly = Integer.parseInt(clientSealBean.getLy());
          int rx = lx + 100;
          int ry = ly + 100;
          donePdfSeal.updateLocationInfoByCoordinate(Integer.parseInt(page), lx, ly, rx, ry);
          donePdfSeal.offsetCoordLocationInfo(Float.parseFloat(clientSealBean.getOffsetX()), Float.parseFloat(clientSealBean.getOffsetY()));
          certPdfData = donePdfSeal.createPdfSeal(Env.timeStampServerUrl, Env.timeStampServerUser, Env.timeStampServerPasswd, certificationLevel);
        } else if ("3".equals(clientSealBean.getType())) {
          donePdfSeal.updateLocationInfoByKeyword(clientSealBean.getKeyword());
          String locationStyle = clientSealBean.getLocationStyle();
          if ("U".equals(locationStyle))
            donePdfSeal.offsetCoordLocationInfo(2);
          else if ("D".equals(locationStyle))
            donePdfSeal.offsetCoordLocationInfo(3);
          else if ("L".equals(locationStyle))
            donePdfSeal.offsetCoordLocationInfo(4);
          else if ("R".equals(locationStyle)) {
            donePdfSeal.offsetCoordLocationInfo(5);
          }
          donePdfSeal.offsetCoordLocationInfo(Float.parseFloat(clientSealBean.getOffsetX()), Float.parseFloat(clientSealBean.getOffsetY()));
          certPdfData = donePdfSeal.createPdfSeal(Env.timeStampServerUrl, Env.timeStampServerUser, Env.timeStampServerPasswd, certificationLevel);
        }
        ClientSealServlet.this.doneMap.put(this.sessionId, certPdfData);
        ClientSealServlet.this.done();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}